package club.jdiy.utils.poi;

import club.jdiy.core.ex.JDiyException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * JDiy数据导入工具类.
 *
 * <p>示例代码参考：
 * <br/> 假设有一个Student学生实体 <br/>
 * 　　实体有字段：serialNo:学号 name:姓名  banji:班级；<br/>
 * 　　excel文件中有这三例数据（学号，姓名，班级)<br/>
 * <pre>
 *
 * List<ImportMeta<Student>> im = new ArrayList<>();　//定义excel中每个列的数据如何导入
 *
 * //定义学号列如何导入：
 * //ImportMeta第1个参数值，应该和Excel对应列的表头标题文字保持一致．
 * //ImportMeta第2个参传入Save处理函数，此处写为lambda表达式: o为Student学生实体对象;第二个参数是从Excel单元格中取到的值（ImportMeta就是要告诉程序，如何把数据值存进实体）：
 *  im.add(new ImportMeta<>("学号", (o, value) -> {
 *      if (value == null || "".equals(value.trim())) throw new JDiyException("学号不能为空");
 *      o.setSerialNo(value);
 *  }));
 *
 *  im.add(new ImportMeta<>("姓名", (o, value) -> {
 *      if (value == null || "".equals(value.trim())) throw new JDiyException("姓名不能为空");
 *      o.setName(value);
 *  }));
 *  im.add(new ImportMeta<>("班级", Student::setBanji));
 * </pre>
 * </p>
 *
 * @param <T> 实体类型
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20210603
 */
public class ExcelImport<T> {
    private final Class<T> tClass;

    /**
     * 构建数据导入处理．
     *
     * @param tClass 持久化实体数据模型类．
     */
    public ExcelImport(Class<T> tClass) {
        this.tClass = tClass;
    }

    /**
     * 执行Excel数据导入.<br/>
     *
     * @param metas       预先定义要导入的列数据转换处理单元(ImportMeta),它是一个List集合，一个下标元素对应Excel中的一列数据（顺序一致）.
     * @param file1       要导入的Excel文件(xls, xlsx格式均可)
     * @param saveHandler 定义并传入一个持久化处理函数，告诉导入程序，如何保存数据．
     * @return 返回成功导入的数据条数.
     * @throws JDiyException 若导入异常，则抛出.
     */
    public int doImport(List<ImportMeta<T>> metas, MultipartFile file1, Save<T> saveHandler) throws JDiyException {
        return doImport(metas, 1, file1, saveHandler);
    }

    /**
     * 执行Excel数据导入.<br/>
     *
     * @param metas       预先定义要导入的列数据转换处理单元(ImportMeta),它是一个List集合，一个下标元素对应Excel中的一列数据（顺序一致）.
     * @param headRowNum  提示表头所在的行，从该行位置开始检查表头，并从它的下一行开始执行数据的导入。
     * @param file1       要导入的Excel文件(xls, xlsx格式均可)
     * @param saveHandler 定义并传入一个持久化处理函数，告诉导入程序，如何保存数据．
     * @return 返回成功导入的数据条数.
     * @throws JDiyException 若导入异常，则抛出.
     */
    public int doImport(List<ImportMeta<T>> metas, int headRowNum, MultipartFile file1, Save<T> saveHandler) throws JDiyException {
        if (!file1.isEmpty()) {
            Workbook book;
            try {
                if (file1.getOriginalFilename() != null && file1.getOriginalFilename().toLowerCase().endsWith(".xlsx")) {
                    book = new XSSFWorkbook(file1.getInputStream());
                } else {
                    book = new HSSFWorkbook(file1.getInputStream());
                }
            } catch (IOException e) {
                throw new JDiyException("无法读取Excel文件，请检查上传文件的有效性." + e.getMessage());
            }
            Sheet sheet = book.getSheetAt(0);
            Row row;
            Cell cell;
            String val;

            StringBuilder errSB = new StringBuilder();
            int okRowsCount = 0;
            int breakNummRowCount = 0;//有时候excel编辑后，删除了一些行后保存导入，会出现提示很大的行号为null报错导致导入失败的bug,加这个参数，判断中间连续空行超过50行时停止导入
            int startRow = headRowNum -1;
            for (int rowNum = startRow; rowNum <= sheet.getLastRowNum(); rowNum++) {
                boolean hasErr = false;
                StringBuilder err = new StringBuilder("行" + (rowNum + 1) + ": ");

                row = sheet.getRow(rowNum);
                if (row == null) {
                    breakNummRowCount++;
                    if (breakNummRowCount > 50) break;
                    continue;
                } else {
                    breakNummRowCount = 0;
                }
                if (rowNum == startRow) {
                    int colNum = 0;
                    boolean checkHeadOk = true;
                    for (ImportMeta<T> im : metas) {
                        cell = row.getCell(colNum++);
                        if (im != ImportMeta.IGNORE && im.getHead() != null) {
                            String rn = getValue(cell);
                            if (!im.getHead().equals(rn)) {
                                checkHeadOk = false;
                                errSB.append("表头检查：第")
                                        .append(colNum)
                                        .append("列，应该是［").append(im.getHead()).append("］,而不是［").append(rn).append("］\n");
                            }
                        }
                    }
                    if (checkHeadOk) continue;
                    else break;
                }

                T vo;
                try {
                    vo = tClass.getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    JDiyException le = new JDiyException(e.getMessage());
                    le.setStackTrace(e.getStackTrace());
                    throw le;
                }
                int colNum = 0;
                for (ImportMeta<T> df : metas) {
                    cell = row.getCell(colNum++);
                    val = cell == null ? null : getValue(cell);
                    try {
                        df.getParser().parse(vo, val);
                    } catch (Exception ex) {
                        hasErr = true;
                        err.append(ex.getMessage()).append("; ");
                    }
                }
                if (hasErr) {
                    errSB.append(err).append("\n");
                } else {
                    try {
                        boolean continue_ =saveHandler.run(vo);
                        okRowsCount++;
                        if(!continue_)break;
                    } catch (Exception ex) {
                        errSB.append(err).append(ex.getMessage()).append("\n");
                        //ex.printStackTrace();
                    }
                }
            }

            String err = errSB.toString();
            if (err.length() > 0) throw new JDiyException(err);
            return okRowsCount;
        } else {
            throw new JDiyException("上传文件不正确。\n");
        }
    }


    public List<T> doImportList(List<ImportMeta<T>> metas, MultipartFile file1) throws JDiyException {
        if (!file1.isEmpty()) {
            Workbook book;
            try {
                if (file1.getOriginalFilename() != null && file1.getOriginalFilename().toLowerCase().endsWith(".xlsx")) {
                    book = new XSSFWorkbook(file1.getInputStream());
                } else {
                    book = new HSSFWorkbook(file1.getInputStream());
                }
            } catch (IOException e) {
                throw new JDiyException("无法读取Excel文件，请检查上传文件的有效性." + e.getMessage());
            }
            Sheet sheet = book.getSheetAt(0);
            Row row;
            Cell cell;
            String val;

            StringBuilder errSB = new StringBuilder();
            int breakNummRowCount = 0;//有时候excel编辑后，删除了一些行后保存导入，会出现提示很大的行号为null报错导致导入失败的bug,加这个参数，判断中间连续空行超过50行时停止导入
            List<T> list = new ArrayList<>();
            for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
                boolean hasErr = false;
                StringBuilder err = new StringBuilder("行" + (rowNum + 1) + ": ");

                row = sheet.getRow(rowNum);
                if (row == null) {
                    breakNummRowCount++;
                    if (breakNummRowCount > 50) break;
                    continue;
                } else {
                    breakNummRowCount = 0;
                }
                if (rowNum == 0) {
                    int colNum = 0;
                    boolean checkHeadOk = true;
                    for (ImportMeta<T> im : metas) {
                        cell = row.getCell(colNum++);
                        String rn = getValue(cell);
                        if (!im.getHead().equals(rn)) {
                            checkHeadOk = false;
                            errSB.append("表头检查：第")
                                    .append(colNum)
                                    .append("列，应该是［").append(im.getHead()).append("］,而不是［").append(rn).append("］\n");
                        }
                    }
                    if (checkHeadOk) continue;
                    else break;
                }

                T vo;
                try {
                    vo = tClass.getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    JDiyException le = new JDiyException(e.getMessage());
                    le.setStackTrace(e.getStackTrace());
                    throw le;
                }
                int colNum = 0;
                for (ImportMeta<T> df : metas) {
                    cell = row.getCell(colNum++);
                    val = cell == null ? null : getValue(cell);
                    try {
                        df.getParser().parse(vo, val);
                    } catch (Exception ex) {
                        hasErr = true;
                        err.append(ex.getMessage()).append("; ");
                    }
                }
                if (hasErr) {
                    errSB.append(err).append("\n");
                } else {
                    try {
                        list.add(vo);
                    } catch (Exception ex) {
                        errSB.append(err).append(ex.getMessage()).append("\n");
                        //ex.printStackTrace();
                    }
                }
            }

            String err = errSB.toString();
            if (err.length() > 0) throw new JDiyException(err);
            return list;
        } else {
            throw new JDiyException("上传文件不正确。\n");
        }
    }


    /**
     * 得到Excel表中的值
     *
     * @param cell Excel中的每一个格子
     * @return Excel中每一个格子中的值
     */
    protected static String getValue(Cell cell) {
        if (cell == null) return null;
        if (cell.getCellType() == CellType.BOOLEAN) {
            // 返回布尔类型的值
            return String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == CellType.NUMERIC) {
            if (DateUtil.isCellDateFormatted(cell)) {
                double d = cell.getNumericCellValue();
                String s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(DateUtil.getJavaDate(d));
                if (s.endsWith(" 00:00:00")) s = s.substring(0, s.length() - 9); //时间没有，去掉时间
                return s;
            } else {
                // 返回数值类型的值
                Number nm = cell.getNumericCellValue();
                if (nm.longValue() == nm.doubleValue()) return String.valueOf(nm.longValue());
                else {
                    return String.valueOf(nm.doubleValue());
                }
            }
        } else if (cell.getCellType() == CellType.FORMULA) {
            String strCell;
            try {
                strCell = String.valueOf(cell.getStringCellValue());
            } catch (IllegalStateException e) {
                strCell = String.valueOf(cell.getNumericCellValue());
            }
            return strCell;
        } else {
            // 返回字符串类型的值
            String s = String.valueOf(cell.getStringCellValue());
            return s == null ? null : s.trim();
        }
    }

}
